﻿using System.Collections.Generic;
using System.Text;
using MLog.Core;

namespace MLog.Helpers {
    public static class FormatterUtils {
        private const string DefaultTimePattern  = "yyyy-MM-dd HH:mm:ss:ffff";
        private const char   StartChar           = '%';
        private const char   ExpressionStartChar = '{';
        private const char   ExpressionEndChar   = '}';
        private const char   TimeChar            = 'd';
        private const char   NameChar            = 'c';
        private const char   LevelChar           = 'p';
        private const char   ThreadChar          = 't';

        private static readonly string Keywords = new string(new[] {TimeChar, NameChar, LevelChar, ThreadChar});

        private static readonly Dictionary<string, string> PatternCache = new Dictionary<string, string>();

        private static bool IsKeyword(char key) {
            return Keywords.IndexOf(key) >= 0;
        }

        /// <param name="pattern"> example: %d{yyyy -MM -dd HH:mm:ss:ffff} %p --- [%t] %-5c</param>
        /// <returns>{0} Time {1} Level {2} ThreadName {3} Name; example: {0:yyyy-MM-dd HH:mm:ss:ffff} {1} --- [{2}] {3,-5}</returns>
        private static string PatternParse(string pattern) {
            var           sb              = new StringBuilder();
            var           index           = 0;
            var           len             = pattern.Length;
            StringBuilder expression      = null;
            StringBuilder padding         = null;
            var           key             = '$';
            var           start           = false;
            var           expressionStart = false;
            var           paddingStart    = false;

            while (index < len) {
                var c = pattern[index];
                if (start) {
                    if (IsKeyword(c))
                        if (key == '$') {
                            key          = c;
                            paddingStart = false;
                            goto A;
                        }

                    if (!expressionStart && c == ExpressionStartChar) {
                        expressionStart = true;
                        expression      = new StringBuilder();
                        goto A;
                    }

                    if (expressionStart && c == ExpressionEndChar) {
                        expressionStart = false;
                        goto A;
                    }

                    if (expressionStart) {
                        expression.Append(c);
                        goto A;
                    }

                    if (paddingStart) {
                        if (padding == null) padding = new StringBuilder();
                        padding.Append(c);
                        goto A;
                    }

                    if (!expressionStart && !paddingStart) {
                        start = false;
                        if (key == TimeChar) {
                            sb.Append("{0:");
                            sb.Append(expression == null ? DefaultTimePattern : expression.ToString());
                        }
                        else if (key == LevelChar) {
                            sb.Append("{1");
                            if (padding != null) sb.Append(',').Append(padding);
                        }
                        else if (key == ThreadChar) {
                            sb.Append("{2");
                            if (padding != null) sb.Append(',').Append(padding);
                        }
                        else if (key == NameChar) {
                            sb.Append("{3");
                            if (padding != null) sb.Append(',').Append(padding);
                        }
                        else {
                            throw new MLogException("key error");
                        }

                        padding    = null;
                        expression = null;
                        key        = '$';
                        sb.Append('}');
                        sb.Append(c);
                    }
                }
                else {
                    if (c == StartChar) {
                        paddingStart = true;
                        start        = true;
                    }
                    else {
                        sb.Append(c);
                    }
                }

                A:
                index++;
            }

            return sb.ToString();
        }

        public static string Format(LoggerEvent @event, string pattern) {
            var sb = new StringBuilder();
            if (!string.IsNullOrEmpty(pattern)) {
                string c;
                if (!PatternCache.ContainsKey(pattern)) {
                    c                     = PatternParse(pattern);
                    PatternCache[pattern] = c;
                }
                else {
                    c = PatternCache[pattern];
                }

                sb.AppendFormat(c, @event.Time, @event.Level, @event.ThreadName, @event.Source.Name);
            }

            var message = @event.Message;
            if (!string.IsNullOrEmpty(@event.Message))
                if (@event.Args != null)
                    sb.AppendFormat(message, @event.Args);
                else
                    sb.Append(message);

            if (@event.Exception != null) sb.AppendLine(ExceptionUtils.Dump(@event.Exception));

            return sb.ToString();
        }

        public static string FormatArray<T>(T[] array, string separator = ",") {
            if (array == null) return "null";

            var length = array.Length;
            var sb     = new StringBuilder();
            for (var i = 0; i < length; i++) {
                sb.Append(array[i]);
                if (i != length - 1) sb.Append(separator);
            }

            return sb.ToString();
        }
    }
}